/*License: CC0 (Public domain) */
#include <stdlib.h>

#define B64ENC_LEN(n) (4 * (((n) + 2) / 3))
#define B64DEC_LEN(b64_sz) ((((b64_sz) / 4) * 3) /*-pad_sz*/)
#define B64DEC_LEN_NO_PAD(b64_sz) B64DEC_LEN(b64_sz + 2)
#define B64PAD_LEN(in_sz) ((3 - ((in_sz) % 3)) % 3)
#define BASE64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
#define BASE64URL "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
#define B64_PAD '='

#ifndef B64_API
#define B64_API
#endif

/// @param 'table' (in) the alphabet
/// @param 'out' (in,out) array of B64ENC_LEN(s_sz) space
B64_API void b64_enc(const char table[static 64], char pad, char *out, const char *s, size_t s_sz);

/// helper to create a reverse lookup table
/// @param '*base64_chars' (in) the alphabet
/// @param 'b64dec' (in,out) array to hold 256 bytes
B64_API void b64dec_init_table(char b64dec[static 256], const char *base64_chars);

/// @param 'b64dec' (in) reverse lookup table
/// @param 'out' (in,out) must have a capacity of B64DEC_LEN(s_sz),
///         not null terminated! (if the input had no padding)
///          write size is B64DEC_LEN(s_sz), real strlen is (B64DEC_LEN(s_sz) - size of padding)
///          padding chars leads to zeros, e.g.: input has a padding of two (pad2 "==" -> "\0\0")
///               or (pad1 '=' -> '\0) or (pad0 "YWIK" -> "ab")
///         to determine the strlen look into your input padding or use B64PAD_LEN
B64_API void b64_dec(const char b64dec[static 256], char pad, char *out, const char *s, size_t s_sz);

/*b64_decode() and b64_encode() are examples*/

/// @return allocated base64 null terminated string; capacity is B64ENC_LEN(s_sz)+1, free it
B64_API char *b64_encode(const char *s, size_t s_sz);

/// @return allocated base64 null terminated string;
///         capacity is B64DEC_LEN(s_sz)+1,
B64_API char *b64_decode(const char *s, size_t s_sz);


#ifndef B64_HEADER
#define B64_HEADER

B64_API void b64_enc(const char table[static 64], char pad, char *out, const char *s, size_t s_sz)
{
  unsigned char b[3];
  int i;
  while (s_sz) {
    for (i = b[0] = b[1] = b[2] = 0; s_sz && i < 3; s_sz--) {
      b[i++] = (unsigned char)*s++;
    }
    *out++ = table[(b[0] >> 2)];
    *out++ = table[(((b[0] & 0x03) << 4) | (b[1] >> 4))];
    *out++ = (i >= 2) ? table[(((b[1] & 0x0F) << 2) | ((b[2] & 0xC0) >> 6))] : pad;
    *out++ = (i == 3) ? table[(b[2] & 0x3F)] : pad;
  }
}

B64_API void b64_dec(const char b64dec[static 256], char pad, char *out, const char *s, size_t s_sz)
{
  unsigned char c[4];
  int i;
  while (s_sz && *s != pad) {
    for (i = c[0] = c[1] = c[2] = c[3] = 0; s_sz && i < 4 && *s != pad; s_sz--) {
      c[i++] = b64dec[(unsigned char)*s++];
    }
    *out++ = (c[0] << 2) | ((c[1] & 0xF0) >> 4); /*pad==*/
    /*if(i >= 3)*/
    *out++ = ((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2); /*pad=*/
    /*if(i >= 4)*/
    *out++ = ((c[2] & 0x03) << 6 | (c[3] & 0x3F));
    /*{ without the "if's" we will write up to 2 extra zeros at the end, in case of padding}*/
  }
}

B64_API void b64dec_init_table(char b64dec[static 256], const char *base64_chars)
{
  for (char i = 0; *base64_chars; base64_chars++, i++) {
    b64dec[(int)*base64_chars] = i;
  }
}

B64_API char *b64_encode(const char *s, size_t s_sz)
{
  size_t out_sz = B64ENC_LEN(s_sz);
  char *out = malloc(out_sz + 1);
  if (out) {
    out[out_sz] = 0;
    b64_enc(BASE64, B64_PAD, out, s, s_sz);
  }
  return out;
}

B64_API char *b64_decode(const char *s, size_t s_sz)
{
  char b64dec[256] = {};
  b64dec_init_table(b64dec, BASE64);
  size_t out_sz = B64DEC_LEN(s_sz);
  char *out = malloc(out_sz+1);
  if (out) {
    out[out_sz] = 0;
    b64_dec(b64dec, B64_PAD, out, s, s_sz);
  }
  return out;
}
#endif

#ifdef B64TEST
#include <unistd.h>
#include <stdio.h>
//cat b64.h | gcc -g3 -Wall -Wextra -fsanitize=address -fsanitize=leak -DB64TEST -x c -
int main()
{
  char line[1024], *out = 0, *in = 0;
  ssize_t n = 0;
  while ((n = read(0,line, 1024)) > 0) {
    out = b64_encode(line, n);
    in = b64_decode(out, B64ENC_LEN(n));
    //printf("%d\nout:%s\nin:%s|\n",B64DEC_LEN(B64ENC_LEN(n)),out,in);
    write(1,in,n);
    free(in);
    in=0;
    free(out);
    out=0;
  }
  free(out);
  free(in);
  return 0;
}

#endif
/* other b64
https://github.com/rafaelsteil/libcgi/blob/master/src/base64.c
*/
